package chair;

import chair.core.Diary;

/**
 * Quelles sont les réflexes que les stimuli peuvent déclencher chez le robot.
 * 
 * Attention, si plusieurs réflexes partagent la même fonction il y aura dans
 * l'ordre d'envoyé : début premier, début second, fin premier, fin second.
 * 
 * FIXME: depuis Organ, quand toggle(off) envoyer à file dans ordre inverse ? Ou
 * avant nouveau réflexe explicitement dire à celui en cours d'arrêter. Dans ce
 * cas toggle(off) signal pour stopper seulement réflexe en cours ?
 * 
 * @author nomhad
 * 
 */
public abstract class Reflex {
	/** Nom associé à ce réflexe */
	private String _name = "Réflexe";
	/** À quelle Function est rattaché ce réflexe */
	private Function _linkedFunction;
	/**
	 * Si != -1 le réflexe n'attendra pas une onde de fin pour se terminer mais
	 * la fin de son timer
	 */
	private long _timer = -1;
	/** Utilisé si timer : à quel moment réflexe a débuté ? */
	private long _timeStarted = -1;
	/** Par défaut un réflexe n'apparaît que pour un stimulus effectif */
	private ReflexType _type = ReflexType.REFLEX;
	/**
	 * Permet de savoir si un réflexe de type LOCK est pioché dans la file d'une
	 * fonction. Vérouillé par Function quand il le défile réflexe (via fire()),
	 * dévérouillé par Organ via releaseLock().
	 **/
	private boolean _locked = false;

	/**
	 * Le type RESPONSE (nom ressorti du placard) pourrait se nommer
	 * "REFLEX_DE_PAVLOV". On définit en effet ici un type de réflexe qui peut
	 * être transférer à un stimulus prédictif. C'est un réflexe
	 * "conditionnable". Typiquement en présence de nourriture un chien bave et
	 * mange. S'il prévois nourriture il va baver (réponse) mais pas manger
	 * (réflexe). Certes c'est faire bien peu de cas du libre-arbitre de ce
	 * pauvre animal.
	 * 
	 * Le type LOCK est un tour utilisé par Organ via ReflexLock afin de
	 * monopoliser une fonction de temps d'effectuer des opérations
	 * particulières. Utilisation déconseillée ailleurs.
	 * 
	 * @author nomhad
	 * 
	 */
	protected enum ReflexType {
		REFLEX, RESPONSE, LOCK
	};

	/**
	 * Getter classique pour type de réflexe. Utilisé par Organ.callResponse()
	 * pour ne déclencher lors de prédiction que les bons réflexes.
	 * 
	 * @return le type du réflexe
	 */
	final ReflexType getType() {
		return _type;
	}

	/**
	 * Permet à un organe de signaler qu'il n'a plus besoin de monopoliser
	 * Function. Celle-ci pourra défiler le réflexe.
	 */
	final void releaseLock() {
		_locked = false;
	}

	/**
	 * Organ doit savoir si a acquis fonction avant de s'exéctuer, Function doit
	 * savoir si libre avant d'aller de l'avant.
	 * 
	 * @return état du verrou
	 */
	final boolean isLocked() {
		return _locked;
	}

	/**
	 * Un réflexe doit être nécessairement relié à une fonction
	 * 
	 * @param fct
	 *            fonction associée
	 */
	protected Reflex(Function fct) {
		plugToFunction(fct);
	}

	/**
	 * Peut donner explicitement un nom au réflexe lors construction
	 * 
	 * @param fct
	 *            fonction associée
	 * 
	 * @param name
	 *            nom du réflexe
	 */
	protected Reflex(Function fct, String name) {
		this(fct);
		_name = name;
	}

	/**
	 * Peut donner explicitement un type au réflexe lors construction
	 * 
	 * @param fct
	 *            fonction associée
	 * 
	 * @param name
	 *            nom du réflexe
	 * @param type
	 *            type du réflexe
	 */
	protected Reflex(Function fct, String name, ReflexType type) {
		this(fct);
		_name = name;
		_type = type;
	}

	/**
	 * Relie le présent réflexe à sa fonction
	 * 
	 * @param fct
	 *            fonction à laquelle est relié le réflexe
	 */
	final private void plugToFunction(Function fct) {
		_linkedFunction = fct;
	}

	/**
	 * Accesseur utilisé dans Striatum.addReflexe()
	 * 
	 * @return fonction associée
	 */
	final Function getFunction() {
		return _linkedFunction;
	}

	/**
	 * Function peut légitimement se renseigner sur existence d'un timer pour ce
	 * réflexe
	 * 
	 * @return true si a une durée d'action fixée, false sinon
	 */
	final boolean hasTimer() {
		return _timer != -1;
	}

	/**
	 * Function indique l'heure qu'il est au réflexe, celui-ci répond s'il est
	 * prêt à s'éteindre. Laisse à Function le soin d'ordonner tous les fire(),
	 * réflexe ne s'éteint pas ici de lui-même.
	 * 
	 * @return true si réflexe toujours en activité, false si vient de se
	 *         terminer
	 */
	final boolean isTimeUp(long time) {
		return (time - _timeStarted > _timer);
	}

	/**
	 * Lors de sa construction la classe fille peut définir timer. timer == 0
	 * permet au réflexe de ne pas attendre de front descendant pour s'éteindre.
	 */
	final protected void setTimer(long timer) {
		_timer = timer;
	}

	/**
	 * Demande à effectuer l'action associée à ce reflexe physiologique. À
	 * utiliser depuis l'extérieur : englobe run() pour permettre de fixer
	 * _timeStarted si présence d'un timer
	 * 
	 * @param impulse
	 *            impulsion à envoyer à ce réflexe
	 */
	final synchronized void fire(Impulse impulse) {
		Diary.logln(_name + ": " + impulse.toString());

		boolean toggle = impulse.getToggle();
		// Function vient de défiler un LOCK : il se vérouille
		if (_type == ReflexType.LOCK && toggle) {
			_locked = true;
		}

		if (_timer != -1) {
			// On allume le feu et il faut le mettre aux poudres
			if (toggle)
				_timeStarted = Chronos.top();
			// Dans le cas contraire on mouille la mêche
			else
				_timeStarted = -1;
		}
		// passe la main à la classe fille
		run(impulse);
	}

	/**
	 * L'action réelle, unique à chaque réflexe. Ce qu'une classe fille de type
	 * REFLEX ou RESPONSE doit redéfinir pour exécuter l'action.
	 * 
	 * @param impulse
	 *            impulsion à envoyer à ce réflexe
	 */
	abstract protected void run(Impulse impulse);

	@Override
	public String toString() {
		return "Reflex [_name=" + _name + ", _timeStarted=" + _timeStarted
				+ ", _timer=" + _timer + ", _type=" + _type + "]";
	};
}
